/**
 * 严肃声明：
 * 开源版本请务必保留此注释头信息，若删除捷码开源〔GEMOS〕官方保留所有法律责任追究！
 * 本软件受国家版权局知识产权以及国家计算机软件著作权保护（登记号：2018SR503328）
 * 不得恶意分享产品源代码、二次转售等，违者必究。
 * Copyright (c) 2020 gemframework all rights reserved.
 * http://www.gemframework.com
 * 版权所有，侵权必究！
 */
package com.gemframework.common.aspect;

import com.gemframework.common.annotation.ApiSign;
import com.gemframework.common.annotation.ApiToken;
import com.gemframework.common.exception.GemException;
import com.gemframework.common.utils.GemHttpUtils;
import com.gemframework.common.utils.GemRedisUtils;
import com.gemframework.common.utils.security.GemCryptosUtils;
import com.gemframework.common.utils.security.GemEncodesUtils;
import com.gemframework.model.enums.ExceptionCode;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
@Aspect // 使用@Aspect注解声明一个切面
@Component
public class SignAspect {

    // App签名密钥
    public static final String APP_SIGN_KEY = "123";

    @Autowired
    GemRedisUtils gemRedisUtils;

    /**
     * 这里我们使用注解的形式 当然，我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method 切点表达式:
     * execution(...)
     */
    @Pointcut(value = "@annotation(com.gemframework.common.annotation.ApiSign)")
    public void sign() {
        
    }
 
    /**
     * 环绕通知 @Around ， 当然也可以使用 @Before (前置通知) @After (后置通知)
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("sign()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 获取SignVaild注解
        if(method.isAnnotationPresent(ApiToken.class)){
            //获取方法上注解中表明的权限
            ApiSign apiSign = method.getAnnotation(ApiSign.class);
            if (apiSign != null) {
                // 注解上的描述
                if(!apiSign.isValid()){
                    //如果通过执行业务逻辑，放行
                    log.info("未开启SignValid，进入业务层处理！");
                    return point.proceed();
                }else {
                    // 签名校验
                    if(signVaild()){
                        log.info("校验通过，进入业务层处理！");
                        return point.proceed();
                    }
                }
            }
        }
        log.info("未检测到@ApiSign注解，进入业务层处理！");
        return point.proceed();
    }


    /**
     * @Title: signVaild
     * @Description: API签名校验
     * ===================
     * 校验参数
     * Api-Versionstring	接口版本号，如1.0
     * Client-OS	        客户端操作系统，android=1，iOS=2，未知=3
     * Client-Typeint	客户端类型，手机=1，平板=2，网页=3
     * Nonce             随机字符串，int型(8位)，如：34887553
     * Sign-Method   	签名算法，请固定使用HMAC-SHA1
     * TimeStamp         客户端当前时间戳，如：1401790196732
     * Sign              签名值
     *  ===================
     */
    private boolean signVaild() {
        HttpServletRequest request = GemHttpUtils.getHttpServletRequest();
        String apiVersion = request.getHeader("Api-Version");
        String clientOs = request.getHeader("Client-OS");
        String clientType = request.getHeader("Client-Type");
        String nonce = request.getHeader("Nonce");
        String signMethod = request.getHeader("Sign-Method");
        String timestamp = request.getHeader("TimeStamp");
        String sign = request.getHeader("Sign");
        StringBuffer sb = new StringBuffer();
        sb.append("Api-Version=" + apiVersion + "&");
        sb.append("Client-OS=" + clientOs + "&");
        sb.append("Client-Type=" + clientType + "&");
        sb.append("Nonce=" + nonce + "&");
        sb.append("Sign-Method=" + signMethod + "&");
        sb.append("TimeStamp=" + timestamp);
        String key = APP_SIGN_KEY + "&";
        // 先将请求参数进行字典排序并且拼接,然后进行urlEncode得到源串
        String source = GemEncodesUtils.urlEncode(parseParamsMap(request.getParameterMap(), sb));
        String result = GemEncodesUtils.encodeBase64(GemCryptosUtils.hmacSha1(source, key).getBytes());
        // 签名错误,抛出异常
        if (!result.equals(sign)) {
            throw new GemException(ExceptionCode.SIGN_ERROR);
        }
        return true;
    }

    /**
     * 将请求参数按字典排序并拼接
     * @param paramsMap
     * @param sb
     * @return
     */
    private String parseParamsMap(Map<String, String[]> paramsMap, StringBuffer sb) {
        if (paramsMap != null) {
            List<String> list = Lists.newArrayList(paramsMap.keySet());
            Collections.sort(list);
            for (String key : list) {
                String[] value = paramsMap.get(key);
                if (sb.length() > 0)
                    sb.append("&");
                sb.append(key + "=" + value[0]);
            }
            return sb.toString();
        }
        return "";
    }

}